/**
 * \file
 * \author  michal.nezerka@gmail.com
 */

#include <iostream>
#include <wx/debug.h>
#include <wx/log.h>
#include <wx/msgdlg.h>
#include <wx/protocol/http.h>
#include <wx/stdpaths.h>
#include <wx/sstream.h>
#include <wx/stream.h>
#include <wx/wfstream.h>
#include "tileprovider.h"
#include "geoutils.h"
#include "httplib.h"

using namespace wildwood;

/// TileWorkerThread //////////////////////////////////////////

TileWorkerThread::TileWorkerThread(TileProvider &tileProvider) : mTileProvider(tileProvider)
{
    wxLogDebug(wxT("TileWorkerThread created"));
    //mHttp.SetHeader(wxT("User-Agent"), wxT("Wildwood"));
}

TileWorkerThread::~TileWorkerThread()
{
    //mHttp.Close();

    //wxLogMessage(wxT("TileWorkerThread destroyed"));
    mQueue.Clear();

    // the thread is being destroyed; make sure not to leave dangling pointers around
    mTileProvider.mWorkerThread = NULL;
}

void *TileWorkerThread::Entry()
{
    wxLogDebug(wxT("TileWorkerThread Entry"));

    while (!TestDestroy())
    {
        // check if there is something in message queue
        TilePos pos;

        wxMessageQueueError err = mQueue.ReceiveTimeout(100, pos);
        if (err == wxMSGQUEUE_NO_ERROR)
        {
            // download tile and notify event listener on success
            if (downloadTile(pos))
            {
                if (mTileProvider.mEventHandler)
                {
                    wxThreadEvent event(wxEVT_THREAD, mTileProvider.mEventId);
                    // send in a thread-safe way
                    wxQueueEvent(mTileProvider.mEventHandler, event.Clone() );
                }
            }
        }
        //wxMilliSleep(1000);
    }
    wxLogDebug(wxT("TileWorkerThread Leave"));
    return NULL;
}

// try to download the tile from tile provider
bool TileWorkerThread::downloadTile(const TilePos pos)
{
    // 1. check if tile file exists
    // this check is necessary since there could be many requests
    // for donwload the same tile. All downloads for existing
    // tile file should be ignored.
    wxFileName tilePath(mTileProvider.getTilePath(pos));
    if (tilePath.FileExists())
        return false;

    // 2. download tile
    wxURL tileUrl = mTileProvider.generateTileUrl(pos, mTileProvider.mTileTemplate);
    wxLogMessage(wxT("Downloading %s path: %s"), tileUrl.GetServer(), tileUrl.GetPath());

    httplib::HttpRequest request(tileUrl);
    request.setUserAgent(wxT("Wildwood"));
    httplib::HttpLib h;
    httplib::HttpResponse *response = h.sendRequest(request);
    if (response->getStatusCode() != 200)
    {
        wxLogError(wxT("Http error, status code: %d, url: %s"), response->getStatusCode(), tileUrl.GetURL());
        return false;
    }

    wxFileOutputStream* outputStream = new wxFileOutputStream(tilePath.GetFullPath());
    const std::vector<char> body = response->getBody();
    outputStream->Write(&body[0], body.size());
    if (!outputStream->IsOk())
    {
        wxStreamError le = outputStream->GetLastError();
        wxLogError(wxT("Error %d saving image data to file: %s"), le, tilePath.GetFullPath());
    }
    outputStream->Close();
    wxDELETE(outputStream);
    wxLogMessage(wxT("Download OK - %s"), tilePath.GetFullPath());

    //wxMilliSleep(300);
    return true;
}

/// TileProvider ////////////////////////////////////////////////

TileProvider::TileProvider(const wxString name, const wxString tpl, wxEvtHandler *eventHandler, const int eventId):
    mEventHandler(eventHandler), mEventId(eventId), mName(name), mTileTemplate(tpl), mWorkerThread(NULL)
{
    mWorkerThread = new TileWorkerThread(*this);
    if (mWorkerThread->Run() != wxTHREAD_NO_ERROR )
    {
        wxLogError("Can't create the tile worker thread!");
        delete mWorkerThread;
        mWorkerThread = NULL;
    }
    // after the call to wxThread::Run(), the m_pThread pointer is "unsafe":
    // at any moment the thread may cease to exist (because it completes its work).
    // To avoid dangling pointers OnThreadExit() will set m_pThread
    // to NULL when the thread dies.
};

TileProvider::~TileProvider()
{
    // empty all allocated bitmaps
    for (std::map<TilePos, Tile>::iterator ii = mTiles.begin(); ii != mTiles.end(); ii++)
    {
        delete (*ii).second.mBitmap;
        (*ii).second.mBitmap = NULL;
    }

    if (mWorkerThread)
    {
        if (mWorkerThread->Delete() != wxTHREAD_NO_ERROR)
            wxLogError("Can't delete the tile worker thread!");
    }
}

wxURL TileProvider::generateTileUrl(const TilePos tilePos, const wxString urlTemplate)
{
    wxString fmt(urlTemplate);
    wxASSERT(fmt.Replace(wxT("ZOOM"), wxT("%d")) == 1);
    wxASSERT(fmt.Replace(wxT("X"), wxT("%d")) == 1);
    wxASSERT(fmt.Replace(wxT("Y"), wxT("%d")) == 1);
    wxURL url(wxString::Format(fmt, tilePos.mZoom, tilePos.mX, tilePos.mY));
    return url;
}

wxFileName TileProvider::getTileDir(unsigned int zoom)
{
    wxFileName imgPath(wxStandardPaths::Get().GetUserLocalDataDir());
    imgPath.AppendDir(wxT("Wildwood"));
    imgPath.AppendDir(mName);
    imgPath.AppendDir(wxString::Format(wxT("%04d"), zoom));
    imgPath.Mkdir(0775, wxPATH_MKDIR_FULL);
    return imgPath;
}

wxFileName TileProvider::getTilePath(const TilePos pos)
{
    wxFileName tilePath(getTileDir(pos.mZoom));
    tilePath.SetName(wxString::Format(wxT("%d-%d"), pos.mX, pos.mY));
    tilePath.SetExt(wxT("png"));
    return tilePath;
}

wxBitmap* TileProvider::getTileImage(const TilePos pos)
{
    // check if tile is valid
    if (pos.mX < 0 || pos.mY < 0 || pos.mX >= pow(2, pos.mZoom) || pos.mY >= pow(2, pos.mZoom))
        return NULL;

    wxBitmap *result = NULL;
    Tile tile;

    // 1. search internal cache
    if (mTiles.count(pos) == 1)
    {
        tile = mTiles.find(pos)->second;
        //std::cout << "Tile found in cache" << std::endl;
        return tile.mBitmap;
    }

    // 2. look for file on filesystem
    wxFileName tilePath(getTilePath(pos));
    wxLogDebug(wxT("Looking for %s"), tilePath.GetFullPath());
    if (!tilePath.FileExists())
    {
        // initiate download
        mWorkerThread->mQueue.Post(pos);
        //mWorkerThread->downloadTile(pos);
        return  NULL;
    }

    // 3. load file
    wxImage img;
    bool loadOk = img.LoadFile(tilePath.GetFullPath());
    if (loadOk)
    {
        result = new wxBitmap(img, wxIMAGE_QUALITY_HIGH);
    } else {
        wxLogError(wxT("Cannot load file %s"), tilePath.GetFullPath());
    }

    wxLogDebug(wxT("Adding tile to cache"));
    tile.mBitmap = result;
    // store tile to cache
    mTiles[pos] = tile;

    return result;
}
